function createRxByReg(regStr) {
    function exec(str) {
        const reg = new RegExp(regStr, 'ug')
        const rs = reg.exec(str)
        if (rs) {
            return {
                result: str.substring(0, reg.lastIndex),
                end: reg.lastIndex,
                match: rs[0]
            }
        }
        return false
    }

    return {exec}
}

// 使用字符串来构建RX
function strRx(s) {
    function exec(str) {
        const rs = str.startsWith(s)
        if (rs) {
            return {
                result: s,
                end: s.length,
                match: s
            }
        }
        return false
    }

    return {exec}
}


function rxExec(rx, str) {
    return rx.exec(str)
}

function rxSeq(...rxList) {
    function exec(str) {
        function loop(rxList, str) {
            if (rxList.length == 0) {
                return {result: '', end: 0, match: []}
            }
            const rx = rxList[0]
            const rxRs = rxExec(rx, str)
            if (rxRs) {
                const subs = str.substring(rxRs.end)
                const leftRs = loop(rxList.slice(1), subs)
                if (leftRs) {
                    return {
                        result: rxRs.result + leftRs.result,
                        end: rxRs.end + leftRs.end,
                        match: [rxRs.match, ...leftRs.match]
                    }
                }
                else {
                    return false
                }
            }
            else {
                return false
            }
        }
        return loop(rxList, str)
    }
    return {exec}
}

function createRxCapture() {
    let envMap = new Map()
    function rxCapture(rx, name) {
        function exec(str) {
            const rxRs = rxExec(rx, str)
            let val = envMap.get(name)
            if (val) {
                val = [val, rxRs.match]
            }
            else {
                val = rxRs.match
            }
            envMap.set(name, val)
            return rxRs
        }
        return {exec}
    }

    function rxBackref(name) {
        function exec(str) {
            const val = envMap.get(name)
            if (str.startsWith(val)) {
                return {
                    result: val,
                    end: val.length,
                    match: val
                }
            }
            return false
        }
        return {exec}
    }
    return {rxCapture, rxBackref}
}


function rxOr(...rxList) {
    function exec(str) {
        for (let rx of rxList) {
            const rs = rxExec(rx, str)
            if (rs) {
                return rs
            }
        }
        return false
    }
    return {exec}
}

function rxRepeat(rx, n, m) {
    if (!n && !m) {
        throw new Error('n m 不能同时为false')
    }
    function exec(str) {
        function loop(str, i, rs) {
            if (m && (i >= m)) {
                return rs
            }
            if (n && (i < n) && (str.length <= 0)) {
                return false
            }
            const rxrs = rxExec(rx, str)

            if (rxrs) {
                return loop(str.substring(rxrs.end), i+1, {
                    result: rs.result + rxrs.result,
                    end: rs.end + rxrs.end,
                    match: [...rs.match, rxrs.match]
                })
            }
            else {
                if (n && i >= n) {
                    return rs
                }
                else {
                    return false
                }
            }
        }
        return loop(str, 0, {result: '', end: 0, match: []})
    }
    return {exec}
}

function rxOneMore(rx) {
    return rxRepeat(rx, 1, false)
}

const emptyRx = {exec: function(str) {
    return {
        result: "",
        end: 0,
        match: ''
    }
}}

function rxZeroOrOne(rx) {
    return rxOr(rx, emptyRx)
}

function rxZeroOrMore(rx) {
    return rxOr(rxOneMore(rx), emptyRx)
}

function rxRepeatJoinBase(rxSep, rx) {
    return rxHandleMatch(rxSeq(rx, rxOneMore(rxSeq(rxSep, rx))), function(match) {
        return [match[0], ...[].concat(...match[1].map(item => { return item[1] }))]
    })
}

function rxRepeatJoinBaseStrict(rxSep, rx) {
    const {rxCapture, rxBackref} = createRxCapture()
    return rxHandleMatch(
        rxSeq(
            rx,
            rxCapture(rxSep, 'sep'),
            rx,
            rxZeroOrMore(rxSeq(rxBackref('sep'), rx))
        ),
        function(match) {
            const rs = [match[0], match[2], ...(match[3] ? match[3].map(item => { return item[1] }) : [])]
            return rs
        }
    )
}



function createRepeatJoin(base) {
    return function(rxSep, rx) {
        return rxOr(
            base(rxSep, rx),
            rxHandleMatch(rx, function(match) {
                return [match]
            })
        )
    }
}

let rxRepeatJoin = createRepeatJoin(rxRepeatJoinBase)
let rxRepeatJoinStrict = createRepeatJoin(rxRepeatJoinBaseStrict)


function rxComplete(rx) {
    function exec(str) {
        const rs = rxExec(rx, str)
        if (rs && rs.result == str) {
            return rs
        }
        return false
    }
    return {exec}
}

function rxHandle(rx, handler) {
    function exec(str) {
        const rs = rxExec(rx, str)
        if (rs) {
            return handler(rs)
        }
        return false
    }
    return {exec}
}


function rxHandleMatch(rx, handler) {
    return rxHandle(rx, rs => { return {...rs, match: handler(rs.match)} })
}

function rxHandleSame(rx) {
    return rxHandle(rx, rs => {
        return {...rs, match: rs.result}
    })
}

function rxBracket(rx) {
    return rxSeq(rxOr(strRx("("), strRx("（")), rx, rxOr(strRx(")"), strRx("）")))
}

const alphaRx = createRxByReg(/^[a-zA-Z]/)  //字母
const hanRx = createRxByReg(/^\p{Unified_Ideograph}/u)  //汉字
const numberRx = createRxByReg(/^[0-9ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ]/)  //数字

module.exports = {createRxByReg, strRx, rxExec, rxSeq, createRxCapture, rxOr, rxRepeat, rxOneMore, emptyRx, rxZeroOrOne, rxZeroOrMore, rxRepeatJoinBase, rxRepeatJoinBaseStrict, rxRepeatJoin, rxRepeatJoinStrict, rxComplete, rxHandle, rxHandleMatch, rxHandleSame, rxBracket, alphaRx, hanRx, numberRx}
